/**
 * *****************************************************************************
 * @file    adt3102_flash.c
 * @author  Home(zhhuang@andartechs.com.cn)
 * @version 1.0.0
 * @date    2021-12-23
 * @brief   The W25X40CL Serial Flash memory,for related configuration operations,
 *          please check the data sheet.
 * 
 *                 Copyright (c) 2021, Andar Technologies Inc.
 *                           www.andartechs.com
 * 
 * *****************************************************************************
 */
/*----------------------------- include --------------------------------------*/
#include "adt3102_flash.h"
#include "adt3102_spi.h"
#include "adt3102_system.h"
#include "adt3102.h"
#include "dsp_ctype_map.h"

#include <stdio.h>
#include <string.h>


/*----------------------------- typedef --------------------------------------*/
typedef enum
{
    WRITE_ENABLE        = 0x06,
    WRITE_DISABLE       = 0x04,
    READ_STATUS_REG     = 0x05,
    WROTE_STATUS_REG    = 0x01,
    READ_DATA           = 0x03,
    FAST_READ           = 0x0B,
    PAGE_PROGRAM        = 0x02,
    SECTOR_ERASE_4KB    = 0x20,
    BLOCK_ERASE_32KB    = 0x52,
    BLOCK_ERASE_64KB    = 0xD8,
    CHIP_ERASE          = 0xC7
} cmdSetTypedef;


/*----------------------------- function --------------------------------------*/
static void Write_Enable(void);
void Write_Disable(void);
static uint8 ReadSR(void);
void WriteSR(uint16 t);
static void Flash_Wait_Busy(void);


/**
 * @brief   Write flash enable (Write Enable Latch(WEL) bit to 1).
 * @param   None.
 * @return  None.
 * @note
 */
static void Write_Enable(void)
{
    /* The Write Enable instruction is entered by driving /CS low */
    spiCsDeselect(SPI0);

    /* Shift the instruction code "06h" into the Data Input(DI) pin */
    spiReadWriteByte(SPI0, WRITE_ENABLE);

    /* And then driving /CS high */
    spiCsSelect(SPI0);

    /* Waiting for the SPI transmission status to become idle status. */
    Wait_bsy(SPI0);
}


/**
 * @brief   Write flash disable (Write Enable Latch(WEL) bit to 0).
 * @param   None.
 * @return  None.
 * @note
 */
void Write_Disable(void)
{
   /* The Write Enable instruction is entered by driving /CS low */
   spiCsDeselect(SPI0);

   /* Shift the instruction code "04h" into the DI pin */
   spiReadWriteByte(SPI0, WRITE_DISABLE);

   /* And then driving /CS high */
   spiCsSelect(SPI0);

   /* Waiting for the SPI transmission status to become idle status. */
   Wait_bsy(SPI0);
}


/**
 * @brief   Read the status register of W25QXX.
 * BIT7  6   5   4   3   2   1   0
 * SPR   RV  TB BP2 BP1 BP0 WEL BUSY
 * @param SPR: setting to 0, status register protect bit, use with WP.
 * @param TB, BP2, BP1, BP0: setting to flash writing protect erea.
 * @param BUSY: busy flag (busy: 1, free: 0)
 * @param WEL: setting to write enable lock.
 * @param defualt :0x00
 * @param   None.
 * @return  uint8.
 * @note
 */
static uint8 ReadSR(void)
{
    uint8 byte = 0;
    spiCsDeselect(SPI0);
    /* Shift the instruction code "05h" into the DI pin */
    spiReadWriteByte(SPI0, READ_STATUS_REG);

    /* Recieve the status */
    byte = spiReadWriteByte(SPI0, 0Xff);
    spiCsSelect(SPI0);

    return byte;
}


/**
 * @brief   Write status register. Only non-volatile Status Register bits.
 *          BIT7   5   4   3   2
 *          SPR    TB BP2 BP1 BP0
 * @param   t: send to data.
 * @return  None.
 * @note    The Write Status Register instruction allows the Status Register to
 *          be written.
 */
void WriteSR(uint16 t)
{
   Write_Enable();

   spiCsDeselect(SPI0);

   spiReadWriteByte(SPI0, WROTE_STATUS_REG);
   spiReadWriteByte(SPI0, t);

   spiCsSelect(SPI0);

   /* Waiting for the SPI transmission status to become idle status. */
   Wait_bsy(SPI0);
   while((ReadSR() & WEL) == WEL);
}


/**
 * @brief   Write status register.
 * @param   None.
 * @return  None.
 * @note
 */
static void Flash_Wait_Busy(void)
{
    while((ReadSR() & BUSY) == BUSY);
}


/**
 * @brief   Reading ID from register.
 * @param   None.
 * @return  uint16.
 * @note
 */
uint16 ReadID(void)
{
    uint16 ID;
    uint8 Manufacturer_ID, Divice_ID;
    spiCsSelect(SPI0);
    delayUs(5, HIGH_CLOCK);

    spiCsDeselect(SPI0);

    spiReadWriteByte(SPI0, 0x90);
    spiReadWriteByte(SPI0, 0X00);
    spiReadWriteByte(SPI0, 0X00);
    spiReadWriteByte(SPI0, 0X00);
    Manufacturer_ID = spiReadWriteByte(SPI0, 0Xff);
    Divice_ID = spiReadWriteByte(SPI0, 0Xff);
    spiCsSelect(SPI0);
    Wait_bsy(SPI0);
    ID = (Manufacturer_ID << 8) + Divice_ID;
    return ID;
}


/**
 * @brief   Reading SPI of flash.
 * @param   pBuffer: Data storage area.
 * @param   ReadAddr: Start reading address.(24-bit)
 * @param   NumByteToRead: The number of bytes to read. Max: 65535.
 * @return  None.
 * @note
 */
void Flash_Read(uint8* pBuffer,uint32 ReadAddr,uint16 NumByteToRead)
{
    uint16 i;

    spiCsSelect(SPI0);
    delayUs(5, HIGH_CLOCK);

    spiCsDeselect(SPI0);

    /* Send to read cmd. */
    spiReadWriteByte(SPI0, READ_DATA);

    /* Send to 24-bit address */
    spiReadWriteByte(SPI0, (uint8)((ReadAddr) >> 16));
    spiReadWriteByte(SPI0, (uint8)((ReadAddr) >> 8));
    spiReadWriteByte(SPI0, (uint8)ReadAddr);

    for(i=0; i < NumByteToRead; i++)
    {
        // Cycle reading.
        pBuffer[i] = spiReadWriteByte(SPI0, 0XFF);
    }
    DSP->dsp_rsv4 = 1;

    Wait_bsy(SPI0);

    spiCsSelect(SPI0);
}


/**
 * @brief   Test download whether sucessful.
 * @param   pArray: test array.
 * @param   pLen:   test len.
 * @return  None.
 * @note
 */
void Flash_ReadCheck(uint8* pBuffer, uint32 StartAddr, uint16 NumByteToWrite)
{
    uint16 pageremain;
    /* The numbers of bytes remaining in a single page. */
    pageremain = 256 - StartAddr % 256;
    /* No more than 255 bytes */
    if(NumByteToWrite <= pageremain) pageremain = NumByteToWrite;
    while(1)
    {
        Flash_Read(pBuffer, StartAddr, pageremain);

        // End of writing.
        if(NumByteToWrite==pageremain) break;
        else // NumByteToWrite>pageremain
        {
            pBuffer     += pageremain;
            StartAddr   += pageremain;
            NumByteToWrite -= pageremain;
            if(NumByteToWrite > 256) pageremain = 256;
            else pageremain = NumByteToWrite;
        }
    }
}


/**
 * @brief   Erase full chip.
 * @param   None.
 * @return  None.
 * @note
 */
void ChipErase(void)
{
    Write_Enable();

    spiCsSelect(SPI0);
    delayUs(5, HIGH_CLOCK);

    spiCsDeselect(SPI0);
    spiReadWriteByte(SPI0, CHIP_ERASE);
    spiCsSelect(SPI0);

    Wait_bsy(SPI0);

    Flash_Wait_Busy();
}


/**
 * @brief   Erase a sector of the chip. size: 4KB
 * @param   Dst_Addr. The sector address, set accroding to actual capacity.
 * @return  None.
 * @note    spiCsDeselect(SPI0) => spiCsSelect(SPI0)
 */
void sectorErase(uint32 Dst_Addr)
{
    Dst_Addr *= 4096;

    /* A Write Enable instruction must be executed before the device will
        accept the Block Erase instruction.
     */
    Write_Enable();

    spiCsDeselect(SPI0);

    /* Earse cmd send to sector. */
    spiReadWriteByte(SPI0, SECTOR_ERASE_4KB);
    /* Send to 24 bit address. */
    spiReadWriteByte(SPI0, (uint8)((Dst_Addr) >> 16));
    spiReadWriteByte(SPI0, (uint8)((Dst_Addr) >> 8));
    spiReadWriteByte(SPI0, (uint8)Dst_Addr);

    spiCsSelect(SPI0);

    /* Waiting for busy bit clear */
    Wait_bsy(SPI0);

    Flash_Wait_Busy();
}


/**
 * @brief   Erase a block of the chip. size: 32KB
 * @param   None.
 * @return  None.
 * @note    spiCsDeselect(SPI0) => spiCsSelect(SPI0)
 */
void blockErase(uint32 Dst_Addr)
{
    /* A Write Enable instruction must be executed before the device will
        accept the Block Erase instruction.
     */
    Write_Enable();

    spiCsDeselect(SPI0);

    /* Earse cmd send to sector. */
    spiReadWriteByte(SPI0, BLOCK_ERASE_32KB);

    // 24-bit address
    spiReadWriteByte(SPI0, (uint8)((Dst_Addr) >> 16));
    spiReadWriteByte(SPI0, (uint8)((Dst_Addr) >> 8));
    spiReadWriteByte(SPI0, (uint8)Dst_Addr);

    spiCsSelect(SPI0);

    Wait_bsy(SPI0);

    Flash_Wait_Busy();
}


/**
 * @brief   SPI Wirites less than 256 bytes of data in a page(0 ~ 65535).
 *          Write a maximum of 256 bytes of data at the specified start.
 * @param   pBuffer: Data storage area.
 * @param   ReadAddr: Start reading address.(24-bit)
 * @param   NumByteToRead: The number of bytes to read. Max: 65535.
 * @return  None.
 * @note
 */
void Write_Page(uint8* pBuffer,uint32 WriteAddr,uint16 NumByteToWrite)
{
    uint16 i;
    /* A Write Enable instruction must be executed before the device will
        accept the Block Erase instruction.
     */
    Write_Enable();

    spiCsDeselect(SPI0);

    /* Send page write cmd. */
    spiReadWriteByte(SPI0, PAGE_PROGRAM);

    /* Send 24-bit address. */
    spiReadWriteByte(SPI0, (uint8)((WriteAddr) >> 16));
    spiReadWriteByte(SPI0, (uint8)((WriteAddr) >> 8));
    spiReadWriteByte(SPI0, (uint8)WriteAddr);

    /* At least one data type. */
    for(i = 0; i < NumByteToWrite; i++)
    {
        // Cycle reading.
        spiReadWriteByte(SPI0, *(pBuffer + i));
    }

    spiCsSelect(SPI0);

    /* The CS/ pin must be held low for the entire length of the instruction
        while data is being sent to the device.
     */
    Flash_Wait_Busy();
}


/**
 * @brief   Wirite SPI Flash without parity.
 * @param   pBuffer: Data storage area.
 * @param   ReadAddr: Start reading address.(24-bit)
 * @param   NumByteToRead: The number of bytes to read. Max: 65535.
 * @return  None.
 * @note    1. It Must be ensured that the data in the written address range 
 *          in all 0xFF, otherwise the data written in non-0xFF will fail.
 *          2. Write data of the sepcified length at the specified address,
 *          but make sure that the address does not cross the boundary!!!
 */
void Write_NoCheck(uint8* pBuffer,uint32 WriteAddr,uint16 NumByteToWrite)
{
    uint16 pageremain;
    /* The numbers of bytes remaining in a single page. */
    pageremain = 256 - WriteAddr % 256;
    /* No more than 255 bytes */
    if(NumByteToWrite <= pageremain) pageremain = NumByteToWrite;
    while(1)
    {
        Write_Page(pBuffer, WriteAddr, pageremain);

        // End of writing.
        if(NumByteToWrite==pageremain) break;
        else // NumByteToWrite>pageremain
        {
            pBuffer     += pageremain;
            WriteAddr   += pageremain;
            NumByteToWrite -= pageremain;
            if(NumByteToWrite > 256) pageremain = 256;
            else pageremain = NumByteToWrite;
        }
    }
}


/**
 * @brief   Wirite SPI Flash have a parity.
 * @param   pBuffer: Data storage area.
 * @param   WriteAddr: Start writing address.(24-bit)
 * @param   NumByteToWrite: The number of bytes to write. Max: 25.
 * @return  None.
 * @note    This function has an erase operation!!!
 */
void Flash_Write(uint8* pBuffer, uint32 WriteAddr, uint16 NumByteToWrite)
{
    uint8 W25QXX_BUFFER[4096];
    uint32 secpos;
    uint16 secoff;
    uint16 secremain;
    uint16 i;
    uint8 * W25QXX_BUF;
    W25QXX_BUF = W25QXX_BUFFER;

    memset(W25QXX_BUFFER, 0x00, sizeof(W25QXX_BUFFER));

    /* Address in sector. */
    secpos = WriteAddr / 4096;

    /* Offset in sector. */
    secoff = WriteAddr % 4096;

    /* The size of the remaining space in the sector. */
    secremain = 4096-secoff;

    // printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);

    /* Not more than 4096 bytes */
    if(NumByteToWrite <= secremain) secremain = NumByteToWrite;
    while(1)
    {
        /* Read the contents of the entire sector. */
        Flash_Read(W25QXX_BUF, secpos * 4096, 4096);

        /* Check data. */
        for(i = 0; i < secremain; i++)
        {
            /* Need to erase. */
            if(W25QXX_BUF[secoff + i] != 0XFF) break;
        }

        /* Need to erase. */
        if(i < secremain)
        {
            /* Erase this sector. */
            sectorErase(secpos);

            /* Copy */
            for(i = 0; i < secremain; i++)
            {
                W25QXX_BUF[i + secoff] = pBuffer[i];
            }

            /* Write to flash */
            Write_NoCheck(W25QXX_BUF,secpos * 4096, 4096);

        }
        /* It is already 0xFF in FLASH, write directly. */
        else
        {
            Write_NoCheck(pBuffer, WriteAddr, secremain);
        }

        /* End of writing. */
        if(NumByteToWrite == secremain)
        {
            break;
        }
        /* No end of writing. */
        else
        {
            /* Sector address +1 */
            secpos++;
            /* Bias address lcation: 0 */
            secoff = 0;

            /* Address offset.
                Write address offset.
                Decreasing number of bytes.
             */
            pBuffer     += secremain;
            WriteAddr   += secremain;
            NumByteToWrite -= secremain;

            /* The next sector still exists. */
            if(NumByteToWrite > 4096)
            {
                secremain=4096;
            }
            /* The next sector does not exists. */
            else
            {
                secremain = NumByteToWrite;
            }
        }
    };
}
